7.1 WebSocket

  1. Motivations
    • HTTP is a stateless protocol. In general, once a URL is submitted to a server, the server-side program (not web server nor app server) usually runs one time, and the server-side program is terminated immediately after sending HTML content or data back to the client-side. It is not easy to use HTTP for real-time and stateful applications that require client-side programs and server-side programs run for a long time, i.e., session.
    • For example, how can you write a chatting application that uses web browsers as UI? Chatting applications need to use the n-1 model, i.e., multiple clients and one server. When a client sends a message to all other clients, the message can be sent to the server, and the server relays the message to all other clients. How?
    • Another example of virtual assistant (I am not sure though whether virtual assistants use WebSocket, but at least the concept of bidirectional communication),
    • Is HTTP a good choice?

  2. Learning outcomes
    • Distinguish stateless applications and stateful applications
    • Use WebSocket for stateful applications
    • Use WebSocket and MongoDB to solve a stateful app: Chatting app
      • Analyze the requirements for a chatting application of the n-1 model
      • Design and implement a chatting application of the n-1 model

  3. WebSocket
    • Read the first paragraph in HTML5 - WebSockets Tutorial and WebSocket.
      • What is WebSocket?
      • What does 'bidirectional communication' mean?
      • When do you use?
      • What port number is used?
    • Web browsers should support WebSocket. What about the server side?
    • Here is an example of echo application that uses WebSocket. Look the source code closely and find what protocol is used. (Note that TRUWSJS (TRU Web Server with JavaScript) should be running. You need to use http, not https.)
    • Read the first three paragraphs in WebSocket.
      • What is 'full-duplex' communications?
      • What is the relationship between HTTP and WebSocket?
      • What port number is used?
      • Can WebSocket server use the port number 80?
      • Can WebSocket server be integrated with a web server?
      • What is a benefit to use WebSocket instead of general TCP connections?

  4. How to use WebSocekt?
    • Open a connection to a WebSocekt server from a client
    • Exchange "message"s
    • Close the connection

  5. How to write a WebSocket client program?
    • Is a WebSocekt client program a web program?
    • Read all in HTML5 - WebSockets Tutorial. They explain the client-side WebSocket as a part of HTML5.
      • Here is an example.
        let ws;
        function connect() {
            if ("WebSocket" in window) {
                ws = new WebSocket("ws://....");  // not HTTP
        
                // register event listeners on the web socket object
                ws.onopen = function() {  // 'open' event listener; when the channel is open
                    .... = 'Connected';
                };
                ws.onmessage = function (eobj) {  // when a message came from the server
                    .... = eobj.data;
                };
                ws.onclose = function() {  // when the channel is closed from the server
                    .... = 'Closed';
                };
                ws.onerror = function() {  // when there is an error
                    ....
                };
            }
        }
                
        function echo() {
            ws.send(....);  // send a message to the server
        }
                
        function end() {
            ws.close();  // close the channel
        }
        
      • List the four events associated with WebSocket object.
        open, close, error, message
      • List the two methods to send a message and close the connection.
        .send(), .close()
      • if ("WebSocket" in window) ???

    • Here is an example of echo application. Check the source code.
      • Can you understand the URL in WebSocket()?
        The URL should start with ws, not http.
      • Can you make multiple connections to the same WebSocket server?
        You can open another window with the same URL.

    • Trial 1. Let's write the echo client program and test it with the echo server, ws://198.162.21.132:8080/~mlee/comp4620/Software/TRUWSJS/rev.4.fork/test_ws_echo_server.wsjs
      Before you test this Trial, make sure you are using http or http://198.162.21.132:8080 for this web page so that this Trial can use http.



  6. How to write a WebSocket server program?
    • There are several implementations of WebSocket server with Node.js. Which implementation is good for this class?

    • As you read in WebSocket, you can write two different types of WebSocket server programs.
      • A WebSocket server program integrated with your Node web server - A WebSocket server program uses the same port number that the web server uses. Do you think a WebSocket server program can be integrated with a HTTPS server too?
      • A standalone WebSocket server program - A WebSocket server program uses a different port number that the web server does not use.

    • Here is the code of WebSocket server that can be used for an echo application.
      // Example of standalone WebSocket echo server with the port number 8888
      const WebSocketServer = require("ws").Server;
      const ws_server = new WebSocketServer({port: 8888});  // Use your port number
          
      ws_server.on("connection", function(ws_client, request) {  // ws_client is an WebSocket object, i.e., a socket for a client.
                                                                 // what if there is a connection request from another clent?
          // Event listeners on the client web socket object
          ws_client.on("message", function(msg) {  // msg is a Blob object.
              msg = String(msg);
              ws_client.send(msg);
              if (msg == "end")  // 'end' is an application level message.
                  ws_client.close();
          });
          ws_client.on("close", function(code) {
              console.log(code);
              ws_client.close();
          });
          ws_client.on("error", function(err) {
              console.log(err);
              ws_client.close();
          });
      });
      

      // WebSocket server integrated with a web serverd that uses the port number 8888
      
      // HTTP server:
      
      const HTTP_PORT_NO = 8888;  // Use your port number
      const http = require("http");
          
      const http_server = http.createServer(function(request, response) {
          ...
      });
      http_server.listen(HTTP_PORT_NO);
      
      // WebSocket server:
      
      const WebSocketServer = require("ws").Server; // WebSocket server integrated with a web server
      const ws_server = new WebSocketServer({server: http_server});  // different from the standalone version
          
      ws_server.on("???", function(ws_client, request) {    // ws_client is an WebSocket object, i.e., a socket for a client.
                                                                   // what if there is a connection request from another clent?
          ws_client.on("???", function(msg) {  // msg is a Blob object.
              msg = String(msg);
              if (msg == "end" || msg == "close" || msg == "quit") {
                  ws_client.???();
              } else {
                  ws_client.???(msg);  // echoing
              }	
          });
          ws_client.on("???", function(msg) {
              console.log('closed');
          });
      });
      
    • List the event[s] associated with WebSocket server object.
    • List the events associated with WebSocket client object.
    • Are multiple connections supported?

    • Trial 2. Let's write the echo server program, ws_echo_server.js, that uses a standalone server, and run it. (You many need to install the 'ws' module.)
      Test your ws echo server with the next code. (Note that you need to use the correct URL of your ws server.)



    • Trial 3. Let's write the echo server program that is integrated with your Node WebServer. You don't have to implement the web server part. Test the server program with the client program in Trial 1 or 2.

    • Is there any way to integrate a ws server with an Express app server? Here is an example. (You many need to install the 'express-ws' module.)
      const express = require("express");
      const app = express();
      
      const expressWs = require("express-ws");
      expressWs(app);
      
      app.listen(8888);  // It should be done after expressWs(app). Use your port number.
      
      // Express service for a route
      app.get("/", function(req, res, next){
          ...
      });
      
      // WebSocekt service for the "/echos" path
      app.ws("/echos", function(wsClient, req) {
          console.log("connected");
          wsClient.on("???", function(msg) {  // msg is a string.
              wsClient.???(msg);
          });
          wsClient.???("???", function(msg) {
              console.log("closed");
          });
      });
      

    • Trial 4. Let's write the echo server program that is integrated with an Express app server. You don't have to implement the Express app server part. Test the server program with the client program in Trial 1 or 2.

  7. A case study: chatting application
    • What kind of chatting service in this class?
      • Multiple users
      • A message to a user from another user
      • A message to all users from a user
    • Requirements
      • User management
        • Join
        • Unsubscribe
        • Sign in
        • Sign out
      • One-to-one chatting
      • Broadcasting

  8. How to design and implement a chatting application?
    • Do you think we can use multiple HTTP methods and paths?
    • As you saw in the previous examples, application-level messages of string type are exchanged between clients and the server. What kind of application-level messages should be exchanged for a chatting app? Any proper data format? (Or can we use parameters or query data?)
      • 'Join' (or 'SignUp') from client to server:
        '{command: join, name: ..., password: ...}'
      • 'Sign in' from client to server:
        '{command: sign_in, name: ..., password: ...}'
      • 'Sign out' ???:
        '{command: sign_out, name: ...}'
      • 'Okay' from server to client:
        '{command: okay, for: ...}'
      • 'Not Okay' from server to client:
        '{command: notokay, for: ...}'
      • 'Signed in' from server to each client so that they can see who is signed in:
        '{command: signed_in, name: ...}'
      • 'Signed out' ???:
        '{command: signed_out, name: ...}'

      • 'One-to-one' chatting from client to server:
        '{command: one_to_one, name: ..., to: ..., message: ...}'
      • 'Broadcasting' ???:
        '{command: broadcasting, name: ..., message: ...}'
      • 'From' from server to client:
        '{command: from, name: ..., message: ...}'
      • 'Error' ???:
        '{command: error, message: ...}'

      • What else do you need?
      • Here is an example how to use the above application-level messages.
        Client-A (Dave)                                     Server                                                     Client-B (John)
                ----------------Connection-------------------->  
                                                            ws_client object created
                ---->'{command: sign_in, name: Dave, ???}'---->  
                <----'{command: okay|notokay, for: sign_in}'--- 
                                                            keep 'Dave' and ws_client socket object in an object
                <----'{command: signed_in, name: John}'--------
                <----'{command: ???, name: Tom}'---------------
                                                               ----------->'{command: signed_in, name: Dave}'------------->
                ---->'{command: one_to_one, name: Dave, to: John, message: Hello}'
                                                               ----------->'{command: from, name: Dave, message: Hello}'-->
                ---->'{command: ???, name: ???, to: John, ???: Bye}'
                                                               ----------->'{command: ???, name: Dave, message: Bye}'----->
                ---->'{command: sign_out, name: Dave}'-------->  
                                                            Delete 'Dave' and ws_client socket object from the object
                                                               ----------->'{command: ???, name: Dave}'------------------->
                <---------------Disconnection----------------->                                                        
        
    • How to implement the above messages?
      • Any good idea?
      • You may review JSON and AJAX.
      • Trial 5. Let's change the echo client program in Trial 1 so that the program can send the JSON strings of the above commands, such as Join, SignIn, SignOut, and OneToOne. You can read the command of JSON string type from the <input>, convert it to an object, convert it back to a JSON string, and send it. Test it with your echo server, or ws://198.162.21.132:8080/~mlee/comp4620/Software/TRUWSJS/rev.4.fork/test_ws_echo_server.wsjs


    • How to implement 'Join' and 'Sign in'?
      • You would better to use a database.
      • Let's use MongoDB. Do you remember how MongoDB is different from MySQL?
    • Client-side: How do you want to design the client user interface?
      • Let's make the user interface simple.
      • What do you need?
        • Menu for signin, signout, join, and unsubscribe, with name and password inputs
        • Textarea for sending messages
        • Two div elements for received messages and the list of signed users
      • Can you make the above user interface quickly?
    • Client-side: How to handle the user messages sent from the server?
      • singed_in
      • signed_out
      • from
      • error
      • Here is an example.
        let ws = new WebSocket("ws://198.162.21.132:8888/");  // should be ws:
        
        ws.onopen = function() {  // Web Socket is connected, send messages using send()
            ...
        };
        
        ws.onmessage = function (evt) { 
            let msgfromserver = JSON.parse(evt.data);
            switch(msgfromserver.command) {
                case 'signed_in':
                    ...
                case 'signed_out':
                    ...
                case 'from':
                    $('#output').append(msgfromserver.name + ': ' + msgfromserver.message + '<br>');
                    ...
                case 'error':
                    ...
            }
        };
        
        ws.onclose = function() {  // websocket is closed.
            ...
        };
        
        ws.onerror = function(evt) { 
            ...
        };
        
        $('#one_to_one').click(function() {   
            let msgtoserver = {};
            msgtoserver.command = 'one_to_one';
            msgtoserver.name = myname;
            msgtoserver.to = $('#receiver').text();
            msgtoserver.message = $('#message').text();
            ws.send(JSON.stringify(msgtoserver));
        });
        ...
        
    • Server-side: How do you want to design the server?
      • Here is the echo-server example that we studied before.
        const WebSocketServer = require('ws').Server,
            ws_server = new WebSocketServer({port: 8888});
            
        ws_server.on('connection', function(ws_client) {  // ws_client is an WebSocket object, i.e., a socket for clients.
            ws_client.on('message', function(msgfromclient) {
                ws_client.send(msgfromclient);
                if (msgfromclient == 'end')
                    ws_client.close();
            });
            ws_client.on('close', function(code) {
                ws_client.close();
            });
            ws_client.on('error', function(err) {
                ws_client.close();
            });
        });
        
      • Which event should be used for the exchange of user messages betweent the client and server?
    • Server-side: How to handle the user messages sent from multiple clients? How to keep multiple connections?
      • join
      • sign_in
        • The client sockets should be saved with the corresponding usernames.
      • sign_out
      • one_to_one
        • The client socket for the receipient should be found.
      • broadcasting
      • Here is an example.
        const WebSocketServer = require('ws').Server,
            ws_server = new WebSocketServer({port: 8888});
        
        let ws_clients = {};  // {userName:ws_client, ...}, where userNames are unique
            
        ws_server.on('connection', function(ws_client) {  // ws_client is an WebSocket object, i.e., a socket for clients.
            ws_client.on('message', function(message) {
                msgfromclient = JSON.parse(message);
                switch(msgfromclient.command) {
                    case 'join':
                        ...
                        break;
                    case 'sign_in':
                        ...
                        ws_clients[msgfromclient.name] = ws_client;
                        ...
                        break;
                    case 'sign_out':
                        ...
                        ??? ws_clients[msgfromclient.name];  // delete the corresponding ws_client
                        ...
                       break;
                    case 'one_to_one':
                        // find the ws_client for msgfromclient.to, and send msgfromclient.message to the ws_client
                        ws_clients[???].send(???);
                        break;
                    case 'broadcasting':
                        ...
                        break;
                    ...
                }
            });
            ws_client.on('close', function(code) {
                ...  // broadcasting it
                ...  // find the socket from ws_clients and delete it
            });
            ws_client.on('error', function(err) {
                ...  // broadcasting it
                ...  // find the socket from ws_clients and delete it
            });
        });
        

  9. References for further information